Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix soundness issue for replace_range and range #81169

Merged
merged 1 commit into from
Jan 19, 2021
Merged

Fix soundness issue for replace_range and range #81169

merged 1 commit into from
Jan 19, 2021

Conversation

dylni
Copy link
Contributor

@dylni dylni commented Jan 18, 2021

Fixes #81138 by only calling start_bound and end_bound once.

I also fixed the same issue for BTreeMap::range and BTreeSet::range.

@rust-highfive
Copy link
Collaborator

r? @withoutboats

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 18, 2021
@camelid camelid added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Jan 18, 2021
@bors
Copy link
Contributor

bors commented Jan 18, 2021

☔ The latest upstream changes (presumably #81159) made this pull request unmergeable. Please resolve the merge conflicts.

@@ -1553,18 +1553,21 @@ impl String {
// Replace_range does not have the memory safety issues of a vector Splice.
// of the vector version. The data is just plain bytes.

match range.start_bound() {
let start = range.start_bound();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think comments explaining this change might be beneficial here as well. With or without a big // WARNING: do not inline this variable :)

Included(&n) => assert!(self.is_char_boundary(n + 1)),
Excluded(&n) => assert!(self.is_char_boundary(n)),
Unbounded => {}
};

unsafe { self.as_mut_vec() }.splice(range, replace_with.bytes());
// Using `range` again would be unsound (#81138)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, here it is. Hmm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bugadani Do you think another comment would be helpful? I added one where I thought it would make the most sense, since adding one above let start but not let end is a little weird.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think more comments wouldn't hurt 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true 😛

@KodrAus
Copy link
Contributor

KodrAus commented Jan 19, 2021

@dylni Would you like to include the example from #81138 as a test case to make sure we are still maintaining the UTF8 invariant?

@@ -25,7 +25,10 @@ where
K: Borrow<Q>,
R: RangeBounds<Q>,
{
match (range.start_bound(), range.end_bound()) {
// Using `range` again would be unsound (#81138)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Using `range` again would be unsound (#81138)
// Using `range` again would be unsound (#81138)
// We assume the bounds reported by `range` remain the same, but
// an adversarial implementation could change between calls

@KodrAus
Copy link
Contributor

KodrAus commented Jan 19, 2021

I've just left a few comments. It would be good to include the EvilRange as a test case, but r=me at any time.

@dylni
Copy link
Contributor Author

dylni commented Jan 19, 2021

Would you like to include the example from #81138 as a test case to make sure we are still maintaining the UTF8 invariant?

Yes, good idea.

@KodrAus
Copy link
Contributor

KodrAus commented Jan 19, 2021

@dylni This looks good to me! Would you like to squash these down then we should be good to go.

@dylni
Copy link
Contributor Author

dylni commented Jan 19, 2021

Thanks @KodrAus! Is this what you had in mind? Squashing is a little awkward with merge commits.

@KodrAus
Copy link
Contributor

KodrAus commented Jan 19, 2021

@bors r+ p=1

@bors
Copy link
Contributor

bors commented Jan 19, 2021

📌 Commit b96063c has been approved by KodrAus

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 19, 2021
@KodrAus
Copy link
Contributor

KodrAus commented Jan 19, 2021

That's it, thanks! A rebase and squash is what I'd meant. We try to avoid merge commits in PRs and leave that to bors to handle.

Thanks for fixing this up 🙇

@bors
Copy link
Contributor

bors commented Jan 19, 2021

⌛ Testing commit b96063c with merge 7d7b22d...

@bors
Copy link
Contributor

bors commented Jan 19, 2021

☀️ Test successful - checks-actions
Approved by: KodrAus
Pushing 7d7b22d to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jan 19, 2021
@bors bors merged commit 7d7b22d into rust-lang:master Jan 19, 2021
@rustbot rustbot added this to the 1.51.0 milestone Jan 19, 2021
@dylni
Copy link
Contributor Author

dylni commented Jan 19, 2021

Oh, thanks, that makes sense.

@ssomers
Copy link
Contributor

ssomers commented Jan 19, 2021

I also fixed the same issue for BTreeMap::range and BTreeSet::range.

Useful and inspirational, but I feel the need to point out that you didn't entirely plug that hole, and probably nobody can. Unlike String::replace_range whose range is tied to usize, the caller of BTreeMap::range gets to choose a type and its Ord implementation. I got a little lost trying to brew a really evil example with a panic, but I still believe it can be done. Here on playground is proof that there are 12 integers from 5 to 7, with the help of an evil borrow.

@dylni
Copy link
Contributor Author

dylni commented Jan 22, 2021

@ssomers I think that's ok. If an impl used by BTreeMap misbehaves, BTreeMap is allowed to misbehave too. The only thing it can't do is something unsound. Although the playground example doesn't give the right result for the number values, it gives the right result based on the Borrow impl.

@ssomers
Copy link
Contributor

ssomers commented Jan 22, 2021

@dylni I agree, but that means there was no unsoundness in BTreeMap's use of start_bound/end_bound, is what I realized this week. How many times these functions are called or what they return doesn't matter for unsoundness. It's what the various invocations of the lookup type's Ord say that might make BTreeMap trip up.

This PR of course still includes a welcome fix to avoid BTreeMap from appearing to misbehave when the range misbehaves. If there was a possibility of unsoundness in BTreeMap, that possibility is still there. And I can't find it.

@dylni
Copy link
Contributor Author

dylni commented Jan 23, 2021

@ssomers You're right. I was looking at the unsafe code here, but it's inside a safe function, so it makes sense that this can't be exploited.

@ssomers
Copy link
Contributor

ssomers commented Jan 23, 2021

Not sure how you arrived in left_edge. But yes, disappointingly, I don't think there's anything to exploit. Essentially, BTreeMap is already prepared to withstand a malicious implementation of Ord for the key type, so the fact that range smuggles in another Ord for its search can't make things worse. When BTreeMap validates the range and panics, it doesn't do that to protect itself from unsoundness, but just to advise the caller that they messed up the range (with a message assuming they have an honest Ord implementation). Not sure if the original authors of the code realized that, but it's written in the contract that it should panic on an invalid range, so I'd better leave that to be.

@dylni
Copy link
Contributor Author

dylni commented Jan 23, 2021

@ssomers left_edge is called here.

When BTreeMap validates the range and panics, it doesn't do that to protect itself from unsoundness, but just to advise the caller that they messed up the range (with a message assuming they have an honest Ord implementation).

I agree, but this was surprising. I think I'll change the comments a little there to make it clearer for people reading it in the future.

JohnTitor added a commit to JohnTitor/rust that referenced this pull request Jan 26, 2021
…ark-Simulacrum

BTreeMap: test all borrowing interfaces and test more chaotic order behavior

Inspired by rust-lang#81169, test what happens if you mess up order of the type with which you search (as opposed to the key type).

r? `@Mark-Simulacrum`
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 18, 2021
…ents, r=m-ou-se

Clarify BTree `range_search` comments

These comments were added by rust-lang#81169. However, the soundness issue [might not be exploitable here](rust-lang#81169 (comment)), so the comments should be updated.

cc `@ssomers`
eggyal pushed a commit to eggyal/copse that referenced this pull request Jan 9, 2023
…-ou-se

Clarify BTree `range_search` comments

These comments were added by #81169. However, the soundness issue [might not be exploitable here](rust-lang/rust#81169 (comment)), so the comments should be updated.

cc `@ssomers`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

String::replace_range is unsound
9 participants